
package com.ys.chatserver.http.file.d;

import com.alibaba.fastjson.JSONObject;
import com.eva.epc.common.file.FileHelper;
import com.eva.epc.common.util.CommonUtils;
import com.eva.framework.httpfile.FileDownload;
import com.eva.framework.utils.LoggerFactory;
import com.ys.chatserver.BaseConf;
import com.ys.chatserver.http.logic.filter.JwtHelper;
import com.ys.chatserver.http.logic.filter.JwtParam;
import com.ys.chatserver.im.ChatServerLauncher;
import com.ys.chatserver.tool.EmptyUtils;
import com.ys.chatserver.tool.MD5Util;
import org.slf4j.Logger;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * 一个图片、语音留言、个人介绍语音、相册照片等的通用下载实用类。
 * <p>
 * <b>补充：</b>下传文件时，Android、iOS、Web等都是可以在通过上传URL中将
 * 参数带过来，所以直接在HttpServletRequest里拿出url里的参数就行了。
 *
 * @since RainbowChat 2.2
 */
//FIXME: 在更苛刻的安全性要求下，可考虑要求客户端把token字段带上来，以便进行更强的合法身份检查
public class BinaryDownloader extends javax.servlet.http.HttpServlet
        implements javax.servlet.Servlet {

    Logger logger = LoggerFactory.getLog();
    /**
     * 一个1像素黑色图片
     */
    public final static String ONE_PIXEL_BLACK_FILE_NAME = "on_pixel_black.png";

    /* (non-Java-doc)
     * @see javax.servlet.http.HttpServlet#HttpServlet()
     */
    public BinaryDownloader() {
        super();
    }

    /**
     * Servlet规范中本protected方法由public void service(,,)方法调用, 而public void service(,,)
     * 则由Web容器调用(所以它是public公共方法),因此一般情况下尽量避免重写public void service(,,)方法
     * ,而重写父类的本protedted方法则比较合理.
     */
    protected void service(HttpServletRequest req, HttpServletResponse res)
            throws ServletException, IOException {
        LoggerFactory.getLog().debug("[HTTP][BINARY_DOWN]下载2进制：你传过来的参数" +
                "action=" + req.getParameter("action")
                + ",file_name=" + req.getParameter("file_name")
                + ",need_dump=" + req.getParameter("need_dump")
                + ",[---- " + req.getContextPath() + "-" + req.getRequestURL() + "]");

        String action = req.getParameter("action");
        String file_name = req.getParameter("file_name");

        //【是否需要转储】：true表示需要转储，否则不需要. 转储是用于图片消息接收方在打开了该图片消息完整图后
        // * 通知服务端将此图进行转储（转储的可能性有2种：直接删除掉、移到其它存储位置），转储的目的是防止大量用户的大量图片
        // * 被读过后还存储在服务器上，加大了服务器的存储压力。<b>注意：</b><u>读取缩略图时无需转储！</u>
        String need_dump = req.getParameter("need_dump");

        // 注意：跟web互通模式开启的情况下，dump参数将强制失败，原因为web端不像app端有缓存能力，如果像app端读后
        //       就dump，则web端下次加载聊天记录时，这个图片或语音等文件就会因被转储而不存在，影响用户体验。-20190330 by Jack Jiang
        boolean needDump = (ChatServerLauncher.bridgeEnabled ? false : (need_dump != null ? "1".equals(need_dump) : false));
        if ("voice_d".equals(action)) {
            String queryString = req.getQueryString();
            String authToken;
            String time;
            String token = req.getParameter("token");
            if (Objects.isNull(token)) {
                token = req.getHeader("token");
                time = req.getHeader("time");
                authToken = req.getHeader("authToken");
            } else {
                time = req.getParameter("time");
                authToken = req.getParameter("authToken");
                queryString = queryString.substring(0, queryString.indexOf("&token"));
            }
            String path = String.format("/api/%s?%s", getServletName(), queryString);

            Map<String, String> map = new HashMap<>();
            map.put("token", token);
            map.put("time", time);
            map.put("path", path);
            String s = MD5Util.MD5_32(JSONObject.toJSONString(map));
            logger.info("--------------token:{}---------time:{}-----------path:{}-----------s:{}----------------", token, time, path, s);
            if (authToken == null) {
                logger.error("----------authTokne is null!----------");
                return;
            }
            if (!authToken.equals(s)) {
                logger.error("----------authToken not equals s!------");
                return;
            }
            if (EmptyUtils.isEmpty(token)) {
                logger.error("-------------token is empty-------------------!");
            }
            JwtParam jwtParam = JwtHelper.parseJwt(token);
            try {
                if (!JwtHelper.tokenAuth(jwtParam)) {
                    logger.error("-------tokenAuth error:{}-------", jwtParam);
                    throw new Exception("暂无访问权限!");
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }


            String userUid = req.getParameter("user_uid");
            // 处理语音留言下载
            processBinaryDownload(res, userUid
                    , BaseConf.getInstance().getDIR_USER_VOICE_UPLOAD_UNREAD(), file_name, FileDownload.CONTENT_TYPE_MP4);
            // 需要转储
            if (needDump) {
                String voiceSrcPath = BaseConf.getInstance().getDIR_USER_VOICE_UPLOAD_UNREAD() + file_name;

                // creates the directory if it does not exist
                File dumpDir = new File(BaseConf.getInstance().getDIR_USER_VOICE_UPLOAD_READ());
                if (!dumpDir.exists())
                    dumpDir.mkdirs();

                // 先把语音留言主文件copy到已读目录（TODO 2014-02-08起为了分析用户行为而转储，以后或可直接删除，因为用户的图片数据意义不大！）
                boolean copySuccess = false;
                try {
                    copySuccess = FileHelper.copyFile(voiceSrcPath
                            , BaseConf.getInstance().getDIR_USER_VOICE_UPLOAD_READ() + file_name);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
                // 最后将语音留言从原目录中删除
                if (copySuccess)
                    FileHelper.deleteFile(voiceSrcPath);
            }
        } else {
            String queryString = req.getQueryString();
            String authToken;
            String time;
            String token = req.getParameter("token");
            if (Objects.isNull(token)) {
                token = req.getHeader("token");
                time = req.getHeader("time");
                authToken = req.getHeader("authToken");
            } else {
                time = req.getParameter("time");
                authToken = req.getParameter("authToken");
                queryString = queryString.substring(0, queryString.indexOf("&token"));
            }
            String path = String.format("/api/%s?%s", getServletName(), queryString);
            Map<String, String> map = new HashMap<>();
            map.put("token", token);
            map.put("time", time);
            map.put("path", path);
            String s = MD5Util.MD5_32(JSONObject.toJSONString(map));
            logger.info("--------------token:{}---------time:{}-----------path:{}-----------s:{}----------------", token, time, path, s);
            if (authToken == null) {
                logger.error("----------authTokne is null!----------");
                return;
            }
            if (!authToken.equals(s)) {
                logger.error("----------authToken not equals s!------");
                return;
            }
            if (EmptyUtils.isEmpty(token)) {
                logger.error("-------------token is empty-------------------!");
            } else {
                JwtParam jwtParam = JwtHelper.parseJwt(token);
                String userUid = jwtParam.getUserId();
                try {
                    if (!JwtHelper.tokenAuth(jwtParam)) {
                        logger.error("-------tokenAuth error:{}-------", jwtParam);
                        throw new Exception("暂无访问权限!");
                    } else {
                        // 图片聊天消息里的图片文件下载
                        if ("image_d".equals(action)) {
                            // 处理图片下载
                            processBinaryDownload(res, userUid
                                    , BaseConf.getInstance().getDIR_USER_IMAGE_UPLOAD_UNREAD()
                                    , file_name, FileDownload.CONTENT_TYPE_JPEG);
                            // 需要转储
                            if (needDump) {
                                String imageSrcPath = BaseConf.getInstance().getDIR_USER_IMAGE_UPLOAD_UNREAD() + file_name;
                                String imageThunmbSrcPath = BaseConf.getInstance().getDIR_USER_IMAGE_UPLOAD_UNREAD() + "th_" + file_name;

                                // creates the directory if it does not exist
                                File dumpDir = new File(BaseConf.getInstance().getDIR_USER_IMAGE_UPLOAD_READ());
                                if (!dumpDir.exists())
                                    dumpDir.mkdirs();

                                // 先把图片主文件copy到已读目录（TODO 2014-02-08起为了分析用户行为而转储，以后或可直接删除，因为用户的图片数据意义不大！）
                                boolean copySuccess = FileHelper.copyFile(imageSrcPath
                                        , BaseConf.getInstance().getDIR_USER_IMAGE_UPLOAD_READ() + file_name);
                                // 再把缩略图copy到已读目录（TODO 2014-02-08起为了分析用户行为而转储，以后或可直接删除，因为用户的图片数据意义不大！）
                                boolean copySuccess_th = FileHelper.copyFile(imageThunmbSrcPath
                                        , BaseConf.getInstance().getDIR_USER_IMAGE_UPLOAD_READ() + "th_" + file_name);
                                // 最后将图片从原目录中删除
                                if (copySuccess && copySuccess_th) {
                                    FileHelper.deleteFile(imageSrcPath);
                                    FileHelper.deleteFile(imageThunmbSrcPath);
                                }
                            }
                        }
                        // 语音留言消息里的语音文件下载（AMR原文件下载）：当前主要用于APP端

                        // 个人照片下载
                        else if ("photo_d".equals(action)) {
                            // 此参数为“1”时，表示当照片图片不存在时，会默认返回一个1像素黑色图片（这主要用于改善Web网页里显示用户
                            // 相册时的体验，否则当用户没有设置头像时，因为没有图片数据返回而会在页面上显示一个“破图”img元素，那就难看死了！）
                            String _onePixelBlackIfNo = req.getParameter("one_pixel_black_if_no");
                            boolean onePixelBlackIfNo = (_onePixelBlackIfNo != null && _onePixelBlackIfNo.equals("1"));

                            File f = new File(BaseConf.getInstance().getDIR_USER_PHOTO_UPLOAD() + file_name);
                            // 为web端的“破图”下载1像素黑色图片，改善视觉体验
                            if (!f.exists() && onePixelBlackIfNo) {
                                File onePixelTransparentAvatarImg = new File(BinaryDownloader.class.getResource(ONE_PIXEL_BLACK_FILE_NAME).toURI());
                                FileDownload.download(res, onePixelTransparentAvatarImg, ONE_PIXEL_BLACK_FILE_NAME, FileDownload.CONTENT_TYPE_JPEG, false);
                            } else {
                                // 正常处理图片下载
                                processBinaryDownload(res, userUid
                                        , BaseConf.getInstance().getDIR_USER_PHOTO_UPLOAD()
                                        , file_name, FileDownload.CONTENT_TYPE_JPEG);
                            }

                            // 如果是综略图的话就不需要尝试更新查看次数了（而且也更新不了，因为db里存放的文件名是全图而非缩略图）
                            if (!file_name.startsWith("th_")) {
                                // 查看次数+1
                            }
                        }
                        // 自我介绍里的语音留言文件下载
                        else if ("pvoice_d".equals(action)) {
                            // 处理语音留言下载
                            processBinaryDownload(res, userUid, BaseConf.getInstance().getDIR_USER_PVOICE_UPLOAD(), file_name);
                            // 查看次数+1
                        }
                        // 群头像的图片文件下载
                        else if ("gavartar_d".equals(action)) {
                            // 此参数为“1”时，表示当群组头像不存在时，会默认返回一个1像素透明图片（这主要用于改善Web网页里显示群组
                            // 头像时的体验，否则当群组头像图片因种种原因没有读取成功时，没有图片数据返回而会在页面上显示一个“破图”img元素，那就难看死了！）
                            String _onePixelTransparentIfNo = req.getParameter("one_pixel_transparent_if_no");
                            boolean onePixelTransparentIfNo = (_onePixelTransparentIfNo != null && _onePixelTransparentIfNo.equals("1"));

                            File f = new File(BaseConf.getInstance().getDIR_USER_AVATARGROUP_DIR() + file_name);
                            // 为web端的“破图”下载1像素透明图片，改善视觉体验
                            if (!f.exists() && onePixelTransparentIfNo) {
                                File onePixelTransparentAvatarImg = new File(BinaryDownloader.class.getResource(UserAvatarDownloader.ONE_PIXEL_TRANSPARENT_AVATAR_FILE_NAME).toURI());
                                FileDownload.download(res
                                        , onePixelTransparentAvatarImg
                                        , UserAvatarDownloader.ONE_PIXEL_TRANSPARENT_AVATAR_FILE_NAME
                                        , FileDownload.CONTENT_TYPE_JPEG
                                        , false);
                            } else {
                                // 处理文件下载
                                processBinaryDownload(res, userUid, BaseConf.getInstance().getDIR_USER_AVATARGROUP_DIR(), file_name);
                            }
                        }
                        // 位置聊天消息里的地图预览图片文件下载
                        else if ("location_d".equals(action)) {
                            // 处理图片下载
                            processBinaryDownload(res, userUid
                                    , BaseConf.getInstance().getDIR_USER_LOCATION_UPLOAD_UNREAD()
                                    , file_name, FileDownload.CONTENT_TYPE_JPEG);
                            // 需要转储
                            if (needDump) {
                                String imageSrcPath = BaseConf.getInstance().getDIR_USER_LOCATION_UPLOAD_UNREAD() + file_name;

                                // creates the directory if it does not exist
                                File dumpDir = new File(BaseConf.getInstance().getDIR_USER_LOCATION_UPLOAD_READ());
                                if (!dumpDir.exists())
                                    dumpDir.mkdirs();

                                // 先把图片主文件copy到已读目录（TODO 转储的目的权是为了分析用户行为而转储，不需要的话可直接删除，因为用户的图片数据意义不大！）
                                boolean copySuccess = FileHelper.copyFile(imageSrcPath
                                        , BaseConf.getInstance().getDIR_USER_LOCATION_UPLOAD_READ() + file_name);

                                // 最后将图片从原目录中删除
                                if (copySuccess) {
                                    FileHelper.deleteFile(imageSrcPath);
                                }
                            }
                        }
                        // 好友备注中的图片下载
                        // TODO: 此下载接口已写好，但因开发计划搁置，客户端暂未实现好友备注中设置图片的功能
                        else if ("remark_photo_d".equals(action)) {
                            // 此参数为“1”时，表示当照片图片不存在时，会默认返回一个1像素黑色图片（这主要用于改善Web网页里显示用户
                            // 相册时的体验，否则当用户没有设置头像时，因为没有图片数据返回而会在页面上显示一个“破图”img元素，那就难看死了！）
                            String _onePixelBlackIfNo = req.getParameter("one_pixel_black_if_no");
                            boolean onePixelBlackIfNo = (_onePixelBlackIfNo != null && _onePixelBlackIfNo.equals("1"));

                            File f = new File(BaseConf.getInstance().getDIR_USER_REMARK_PHOTO_UPLOAD() + file_name);
                            // 为web端的“破图”下载1像素黑色图片，改善视觉体验
                            if (!f.exists() && onePixelBlackIfNo) {
                                File onePixelTransparentImg = new File(BinaryDownloader.class.getResource(ONE_PIXEL_BLACK_FILE_NAME).toURI());
                                FileDownload.download(res, onePixelTransparentImg, ONE_PIXEL_BLACK_FILE_NAME, FileDownload.CONTENT_TYPE_JPEG, false);
                            } else {
                                // 正常处理图片下载
                                processBinaryDownload(res, userUid, BaseConf.getInstance().getDIR_USER_REMARK_PHOTO_UPLOAD(), file_name, FileDownload.CONTENT_TYPE_JPEG);
                            }
                        } else {
                            LoggerFactory.getLog().warn("[HTTP]无效的action=" + action);
                        }
                    }
                } catch (IOException e) {
                    throw e;
                } catch (Exception e2) {
                    throw new IOException(e2.getMessage(), e2.getCause());
                }
            }
        }
    }

    /**
     * HTTP文件下载实现方法.
     * <p>
     * 本方法没有任何异常地返回即意味着成功处理完成.
     *
     * @param file_dir  要下载的文件所位于的服务器磁盘目录（此dir的结尾需要"/"哦）
     * @param file_name 要下载的磁盘文件名
     */
    private void processBinaryDownload(HttpServletResponse res
            , String userUid, String file_dir, String file_name) throws IOException {
        this.processBinaryDownload(res, userUid, file_dir, file_name, FileDownload.CONTENT_TYPE_DEFAULT);
    }

    /**
     * HTTP文件下载实现方法.
     * <p>
     * 本方法没有任何异常地返回即意味着成功处理完成.
     *
     * @param file_dir  要下载的文件所位于的服务器磁盘目录（此dir的结尾需要"/"哦）
     * @param file_name 要下载的磁盘文件名
     */
    private void processBinaryDownload(HttpServletResponse res
            , String userUid, String file_dir, String file_name
            , String contentType) throws IOException {
        try {
            if (!CommonUtils.isStringEmpty(file_name, true)) {
                LoggerFactory.getLog().debug("[HTTP]用户【" + userUid + "】要下载的2进制文件 " + file_name
                        + "将立即开始处理过程 ......");
                // 实施文件下载
                FileDownload.download(res, file_dir + file_name, file_name, contentType
                        , (FileDownload.CONTENT_TYPE_JPEG.equals(contentType) || FileDownload.CONTENT_TYPE_MP4.equals(contentType)) ? false : true);
            } else
                LoggerFactory.getLog().warn("[HTTP]对不起,用户【" + userUid + "】要下载的2进制(file_name=" + file_name + ")不存在！");
        } catch (Exception e) {
            LoggerFactory.getLog().warn("[HTTP]用户【" + userUid + "】的下载的2进制文件不存在，本次下次已提前结束(" + e.getMessage() + ")");
        }
    }


}
